﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Security;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Xml;
using System.Xml.Serialization;
using Elderos.Controls;
using Elderos.Dialogs;
using Elderos.Utils;
using FontDialogSample;
using Microsoft.Samples.CustomControls;
using Microsoft.Win32;
using TreeBuilder.Help;
using TreeBuilder.Settings;
using Path = System.IO.Path;

namespace TreeBuilder
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {

        private static int _documentsCreated;

        private readonly ExceptionHandler _exceptionHandler = new ExceptionHandler();

        private readonly ObservableCollection<Tree> _trees = new ObservableCollection<Tree>();

        private readonly Dictionary<string, string> _extensionDescriptions =
            new Dictionary<string, string>
                {
                    {"tree", "Tree format (reopenable)"},
                    {"bmp", "Raw bitmap file"},
                    {"jpeg", "Jpeg file"},
                    {"png", "Png file"}
                };

        public MainWindow()
        {
            InitializeComponent();
            MainRibbon.IsMinimized = ApplicationSettings.Default.RibbonMinimized;
            TabControl.DataContext = _trees;
            OpenLastOpenedTrees();
            UpdateFontsGallery();
        }

        private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _exceptionHandler.Attempt(OpenTree);
        }

        private void OpenTree()
        {
            var dialog =
                new OpenFileDialog
                    {
                        DefaultExt = "tree",
                        AddExtension = true,
                        Filter = "Tree format|*.tree|All formats|*.*",
                        Multiselect = true
                    };

            bool? result = dialog.ShowDialog();
            if (!result.Value)
                return;

            try
            {
                foreach (var fileName in dialog.FileNames)
                {
                    OpenFromFile(fileName);
                }
            }
            catch (SecurityException ex)
            {
                MsgBox.Show("Cannot open the file due to a security reason. Make sure you have enough rights to access this file."
                            + Environment.NewLine
                            + "The message from system was:"
                            + Environment.NewLine
                            + ex.Message,
                            "Security exception",
                            "Error",
                            MsgBoxButtons.Ok,
                            MsgBoxImages.Stop
                    );
                return;
            }
            catch(InvalidOperationException)
            {
                string extension = Path.GetExtension(dialog.FileName);
                if (_extensionDescriptions.Any(x => "." + x.Key == extension)
                    && extension != "tree")
                {
                    MsgBox.Show(
                        "Cannot open the file. It seems like you're trying to open an image. Only *.tree or valid *.xml format files are supported.",
                        "Invalid format",
                        "Error",
                        MsgBoxButtons.Ok,
                        MsgBoxImages.Stop);
                    return;
                }

                MsgBox.Show(
                    "Cannot open the file. Make sure you're trying to open a valid *.tree or *.xml file.",
                    "Invalid format",
                    "Error",
                    MsgBoxButtons.Ok,
                    MsgBoxImages.Stop
                    );
                return;
            }
        }

        public void OpenFromFile(string filename)
        {
            var serializer = new XmlSerializer(typeof(Tree));

            using (StreamReader reader = File.OpenText(filename))
            using (XmlReader xmlReader = XmlReader.Create(reader))
            {
                var tree = (Tree)serializer.Deserialize(xmlReader);
                tree.Filename = filename;
                tree.OpeningFromFile = true;
                tree.IsSaved = true;
                tree.Loaded += Tree_Loaded;
                tree.SelectionChanged += SelectionChanged;

                _trees.Add(tree);

                TabControl.SelectedItem = tree;
            }
        }

        private void Tree_Loaded(object sender, RoutedEventArgs e)
        {
            Tree tree = (Tree) sender;
            tree.OpeningFromFile = false;
            tree.Loaded -= Tree_Loaded;
        }

        private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            CreateNewTree();
        }

        public void CreateNewTree()
        {
            while (true)
            {
                _documentsCreated++;
                if (TabControl.Items.Cast<Tree>().All(x => x.DocumentNo != _documentsCreated))
                    break;
            }

            var tree = new Tree {DocumentNo = _documentsCreated};
            tree.SelectionChanged += SelectionChanged;
            tree.IsSaved = true;
            _trees.Add(tree);

            TabControl.SelectedItem = tree;
        }

        private void CloseCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _exceptionHandler.Attempt(
                () =>
                    {
                        Tree tree;
                        if ((string)e.Parameter == "current")
                        {
                            tree = (Tree) TabControl.SelectedItem;
                        }
                        else
                        {
                            Button btn = (Button)e.OriginalSource;
                            TabItem item = btn.TryFindParent<TabItem>();
                            tree = (Tree) item.Content;
                        }
                        CloseTree(tree);
                    }
                );
        }

        private void CloseTree(Tree tree)
        {
            if (!CheckTree(tree)) return;

            if (!tree.IsSaved)
            {
                var response = MsgBox.Show("There are some unsaved changes on this tree." + Environment.NewLine +
                                           "Do you want to save it?",
                                           "Save tree?",
                                           "Close tree",
                                           MsgBoxButtons.YesNoCancel,
                                           MsgBoxImages.Question
                    );

                if (response == DialogResponse.None) return;
                if (response == DialogResponse.Yes)
                {
                    bool success = _exceptionHandler.Attempt(() => SaveTree("tree", tree, false));
                    if (!success)
                    {
                        var result =
                            MsgBox.Show("Saving tree operation ended unsuccessfully." + Environment.NewLine +
                                        "Do you want to close the tree anyway?",
                                        "Fail",
                                        "Save tree", MsgBoxButtons.YesNo, MsgBoxImages.Warning);
                        if (result == DialogResponse.No) return;
                    }
                }
            }

            _trees.Remove(tree);
            tree.SelectionChanged -= SelectionChanged;
        }

        private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _exceptionHandler.Attempt(
                () => SaveTree((string) e.Parameter, GetActiveTree(), false)
                );
        }

        private void SaveTree(string extension, Tree tree, bool needNewFilename, bool showMessage = true)
        {
            if (!CheckTree(tree)) return;

            tree.UnselectAllElements();

            string path;
            if (extension == "tree" && !needNewFilename)
                path = tree.Filename ?? GetSavePath(tree.DisplayName, extension);
            else
                path = GetSavePath(tree.DisplayName, extension);

            if (path == null) return;

            if (extension == "tree")
            {
                SaveToXml(tree, path);
                tree.Filename = path;
                tree.IsSaved = true;
            }
            else if (_extensionDescriptions.ContainsKey(extension))
            {
                SaveToImage(tree, extension, path);
            }
            if (showMessage)
                MsgBox.Show("Tree saved to " + path + " successfully.", "Save tree", "Success");
        }

        private bool CheckTree(Tree tree)
        {
            if (tree == null)
            {
                MsgBox.Show("No tree selected", "Error", "Error", MsgBoxButtons.Ok, MsgBoxImages.Stop);
                return false;
            }
            return true;
        }

        private void SaveToImage(Tree tree, string extension, string path)
        {
            var margin = ImageSettings.Default.FrameThickness;
            var builder = new BitmapBuilder(margin, ImageSettings.Default.DpiX, ImageSettings.Default.DpiY);

            builder.SaveImage(tree, path, extension);
            
        }

        private string GetSavePath(string treeName, string extension)
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.DefaultExt = extension;
            dialog.OverwritePrompt = true;
            dialog.AddExtension = true;
            if (treeName != null)
                dialog.Title = "Saving tree \"" + treeName + "\"";
            dialog.Filter = _extensionDescriptions[extension] +  "|*." + extension;
            bool? result = dialog.ShowDialog();
            if (!result.Value)
                return null;

            return dialog.FileName;
        }

        private Tree GetActiveTree()
        {
            return (Tree)TabControl.SelectedItem;
        }

        private static void SaveToXml(Tree tree, string path)
        {
            var serializer = new XmlSerializer(typeof(Tree));

            using (StreamWriter streamWriter = File.CreateText(path))
            using (XmlWriter xmlWriter = XmlWriter.Create(streamWriter))
            {
                serializer.Serialize(xmlWriter, tree);
                xmlWriter.Flush();
            }
        }


        private void SaveAsCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _exceptionHandler.Attempt(
                () => SaveTree("tree", GetActiveTree(), true)
                );
        }

        private void PrintCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Tree activeTree = GetActiveTree();
            _exceptionHandler.Attempt(() => PrintTree(activeTree));
        }

        private void PrintTree(Tree tree)
        {
            if (!CheckTree(tree)) return;

            var printDialog = new PrintDialog();
            bool? dlgResult = printDialog.ShowDialog();
            if (!dlgResult.Value) return;


            var margin = ImageSettings.Default.FrameThickness;

            double dpiX = printDialog.PrintTicket.PageResolution.X.HasValue
                              ? printDialog.PrintTicket.PageResolution.X.Value
                              : ImageSettings.Default.DpiX;

            double dpiY = printDialog.PrintTicket.PageResolution.Y.HasValue
                              ? printDialog.PrintTicket.PageResolution.Y.Value
                              : ImageSettings.Default.DpiY;

            var builder = new BitmapBuilder(margin, dpiX, dpiY);

            BitmapSource image = builder.BuildImage(tree);

            Rect rect = new BitmapBuilder(margin, 96, 96).GetTreeBounds(tree);
            
            DrawingVisual visual = new DrawingVisual();
            using (var context = visual.RenderOpen())
            {
                context.DrawImage(image,
                                  new Rect(margin.Left + ApplicationSettings.Default.PrinterBorders,
                                           margin.Top + +ApplicationSettings.Default.PrinterBorders,
                                           rect.Width + margin.Left + margin.Right,
                                           rect.Height + margin.Top + margin.Bottom));
            }

            printDialog.PrintVisual(visual, tree.Filename);
        }

        private void ExitCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Close();
        }

        private void OptionsCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var optionsWnd = new OptionsWindow();
            optionsWnd.Owner = this;

            if (!optionsWnd.ShowDialog().Value) return;

            optionsWnd.ApplySettings();
        }

        private void HelpCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            new HelpWindow().Show();
        }

        private void AboutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            new AboutBox(this).ShowDialog();
        }

        private void RibbonWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!Exit()) 
                e.Cancel = true;
            RememberOpenedTrees();
            RememberFontsUsed();
        }

        public const string LastOpenedFilename = "LastOpenedTrees.txt";

        private void RememberOpenedTrees()
        {
            var fileLocations = TabControl.Items.OfType<Tree>()
                .Where(x => x.IsSaved)
                .Where(x => x.Filename != null)
                .Select(x => x.Filename)
                .ToArray();

            string strToWrite =
                fileLocations.Length < 1
                    ? ""
                    : string.Join(Environment.NewLine, fileLocations);

            WriteToIsolatedStorage(strToWrite, LastOpenedFilename);
        }

        private void OpenLastOpenedTrees()
        {
            var filename = LastOpenedFilename;
            var lines = ReadFromIsolatedStorage(filename);
            if (lines == null) return;

            foreach (var line in lines)
            {
                TryOpenFile(line);
            }
        }

        private static List<string> ReadFromIsolatedStorage(string filename)
        {
            var lines = new List<string>();
            using (var storage = IsolatedStorageFile.GetUserStoreForAssembly())
            {
                if (!storage.FileExists(filename)) return null;

                using (var file = storage.OpenFile(filename, FileMode.Open))
                using (var reader = new StreamReader(file, Encoding.UTF8))
                {
                    while (!reader.EndOfStream)
                    {
                        lines.Add(reader.ReadLine());
                    }
                }
            }
            return lines;
        }

        private void TryOpenFile(string filename)
        {
            try
            {
                if (!File.Exists(filename)) return;
                OpenFromFile(filename);
            }
            catch (Exception)
            { }
        }

        private void WriteToIsolatedStorage(string strToWrite, string filename)
        {
            using (var storage = IsolatedStorageFile.GetUserStoreForAssembly())
            {
                if (storage.FileExists(filename))
                    storage.DeleteFile(filename);
                var bytes = Encoding.UTF8.GetBytes(strToWrite);
                if (storage.AvailableFreeSpace < bytes.Length)
                {
                    return;
                }
                using (var file = storage.CreateFile(filename))
                {
                    file.Write(bytes, 0, bytes.Length);
                }
            }
        }

        private const string FontsUsedFilename = "LastUsedFonts.txt";

        private void RememberFontsUsed()
        {
            var serialized =
                FontGallery.Items.Cast<Font>()
                    .Select(XmlSerializationUtility.Serialize)
                    .Select(x => x.Replace(Environment.NewLine, ""));
            var strToWrite = string.Join(Environment.NewLine, serialized);
            WriteToIsolatedStorage(strToWrite, FontsUsedFilename);
        }

        private void UpdateFontsGallery()
        {
            var lines = ReadFromIsolatedStorage(FontsUsedFilename);
            if (lines == null) return;

            var fonts = lines.Select(XmlSerializationUtility.Deserialize<Font>);
            FontGallery.Items.Clear();
            foreach (var font in fonts)
            {
                FontGallery.Items.Add(font);
            }
        }

        private bool Exit()
        {
            List<string> unsaved = _trees.Where(x => !x.IsSaved).Select(x => x.DisplayName).ToList();

            if (unsaved.Count <= 0) return true;

            string msgBoxText = "There are some unsaved trees:" 
                + Environment.NewLine 
                + string.Join(Environment.NewLine, unsaved)
                + Environment.NewLine
                + "Do you want to save them?";

            var dialogResult = MsgBox.Show(msgBoxText, "Save trees?", "Closing TreeBuilder", MsgBoxButtons.YesNoCancel,
                                           MsgBoxImages.Question);

            if (dialogResult == DialogResponse.None) return false;

            if (dialogResult == DialogResponse.No) return true;

            //dialogResult == DialogResponse.Yes
            _trees.Where(x => !x.IsSaved).ToList().ForEach(x => SaveTree("tree", x, false, false));

            return true;
        }

        private void SaveAll_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            _exceptionHandler.Attempt(
                () =>
                    {
                        foreach (var tree in TabControl.Items.OfType<Tree>())
                        {
                            SaveTree("tree", tree, false, false);
                        }
                    }
            );
        }

        private void CloseUpdateInfo_Click(object sender, RoutedEventArgs e)
        {
            HideUpdateNotification();
        }

        private void HideUpdateNotification()
        {
            UpdateNotificationBorder.Visibility = Visibility.Collapsed;
            Grid.SetRowSpan(TabControl, 2);
        }

        private void UpdateUri_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            HideUpdateNotification();
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            e.Handled = true;
        }

        private void HideFirstTimeTips()
        {
            FirstTimeTipsBorder.Visibility = Visibility.Collapsed;
            Grid.SetRowSpan(TabControl, 2);
        }

        private void CloseFirstTimeTips_Click(object sender, RoutedEventArgs e)
        {
            HideFirstTimeTips();
        }

        private void SelectionChanged()
        {
            var tree = GetActiveTree();
            var nodesSelected = tree.TreeCanvas.Children.OfType<INode>().Any(x => x.IsSelected);
            NodeContextualGroup.Visibility = nodesSelected ? Visibility.Visible : Visibility.Collapsed;
        }

        private void SetFont_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            var tree = GetActiveTree();
            e.CanExecute = tree.TreeCanvas.Children.OfType<TreeNode>().Any(x => x.IsSelected);
        }

        private void SetFont_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var tree = GetActiveTree();

            var font = (Font) e.Parameter;

            var selectedNodes = tree.TreeCanvas.Children.OfType<TreeNode>().Where(x => x.IsSelected).ToArray();
            var actions = new List<UndoableAction>();
            foreach (var node in selectedNodes)
            {
                var action = new ChangeFontAction(node, font);
                actions.Add(action);
            }
            var combined = new CombinedAction(actions);
            tree.History.ExecuteAction(combined);
        }
    }
}
